﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace CPPEI.Net
{
    [AttributeUsage(AttributeTargets.Property)]
    public class FieldAttribute : Attribute
    {
        public int Order { get; set; }

        public int Length { get; set; }
    }

    public class FieldDescription
    {
        public int Order { get; set; }
        public int Length { get; set; }
        public PropertyInfo Property { get; set; }

        public void ToBuffer(object obj, byte[] buffer, ref int offset)
        {
            if (Property.PropertyType == typeof(byte))
            {
                BufferUtil.ByteToBuffer((byte)Property.GetValue(obj), buffer, ref offset);
            }
            else if (Property.PropertyType == typeof(int))
            {
                BufferUtil.IntToBuffer((int)Property.GetValue(obj), buffer, ref offset);
            }
            else if (Property.PropertyType == typeof(double))
            {
                BufferUtil.DoubleToBuffer((double)Property.GetValue(obj), buffer, ref offset);
            }
            else if (Property.PropertyType == typeof(string))
            {
                BufferUtil.StringToBuffer((string)Property.GetValue(obj), buffer, ref offset, Length);
            }
            else if (Property.PropertyType == typeof(bool))
            {
                BufferUtil.BooleanToBuffer((bool)Property.GetValue(obj), buffer, ref offset);
            }
            else if (Property.PropertyType == typeof(byte[]))
            {
                BufferUtil.BufferToBuffer((byte[])Property.GetValue(obj), buffer, ref offset, Length);
            }
        }

        public void FromBuffer(object obj, byte[] buffer, ref int offset)
        {
            if (Property.PropertyType == typeof(byte))
            {
                Property.SetValue(obj, BufferUtil.ByteFromBuffer(buffer, ref offset));
            }
            else if (Property.PropertyType == typeof(int))
            {
                Property.SetValue(obj, BufferUtil.IntFromBuffer(buffer, ref offset));
            }
            else if (Property.PropertyType == typeof(double))
            {
                Property.SetValue(obj, BufferUtil.DoubleFromBuffer(buffer, ref offset));
            }
            else if (Property.PropertyType == typeof(string))
            {
                Property.SetValue(obj, BufferUtil.StringFromBuffer(buffer, ref offset, Length));
            }
            else if (Property.PropertyType == typeof(bool))
            {
                Property.SetValue(obj, BufferUtil.BooleanFromBuffer(buffer, ref offset));
            }
            else if (Property.PropertyType == typeof(byte[]))
            {
                Property.SetValue(obj, BufferUtil.BufferFromBuffer(buffer, ref offset, Length));
            }
        }
    }

    public class DataDescription
    {
        public Type DataContractType { get; set; }

        public List<FieldDescription> Fields { get; set; }

        public int Size { get; private set; }

        public DataDescription()
        {
            Fields = new List<FieldDescription>();
        }

        public void InsertASC(FieldDescription field)
        {
            for (int index = 0; index < Fields.Count; index++)
            {
                if (Fields[index].Order > field.Order)
                {
                    Fields.Insert(index, field);
                    Size += field.Length;
                    return;
                }
            }
            Fields.Add(field);
            Size += field.Length;
        }
    }

    public class DataDescriptionCache
    {
        private static DataDescriptionCache s_cache;

        public static DataDescriptionCache Instance
        {
            get
            {
                if (s_cache == null)
                {
                    s_cache = new DataDescriptionCache();
                }
                return s_cache;
            }
        }

        public List<DataDescription> Datas { get; private set; }

        private DataDescriptionCache()
        {
            Datas = new List<DataDescription>();
        }

        public DataDescription GetDescription(Type dataType)
        {
            for (int index = 0; index < Datas.Count; index++)
            {
                if (Datas[index].DataContractType == dataType)
                {
                    return Datas[index];
                }
            }

            DataDescription dataDescription = new DataDescription();

            PropertyInfo[] propertyInfoArr = dataType.GetProperties();
            foreach (PropertyInfo propertyInfo in propertyInfoArr)
            {
                FieldAttribute fieldAttr = propertyInfo.GetCustomAttribute<FieldAttribute>();
                if (fieldAttr != null)
                {
                    FieldDescription fieldDescription = new FieldDescription();
                    fieldDescription.Order = fieldAttr.Order;
                    fieldDescription.Length = GetLength(propertyInfo, fieldAttr);
                    fieldDescription.Property = propertyInfo;

                    dataDescription.InsertASC(fieldDescription);
                }
            }

            this.Datas.Add(dataDescription);

            return dataDescription;
        }
        private int GetLength(PropertyInfo property, FieldAttribute attribute)
        {
            if (property.PropertyType == typeof(byte)
                || property.PropertyType == typeof(bool))
            {
                return 1;
            }
            if (property.PropertyType == typeof(short))
            {
                return 2;
            }
            if (property.PropertyType == typeof(int)
                || property.PropertyType == typeof(uint))
            {
                return 4;
            }
            if (property.PropertyType == typeof(double)
                || property.PropertyType == typeof(long))
            {
                return 8;
            }
            return attribute.Length;
        }
    }
}
